package com.zs.batch.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.retry.support.RetryTemplateBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * @author lanyuping
 * @date 2022/10/19 11:14
 */
@Configuration
public class BatchConfig {
    private static final Logger logger = LogManager.getLogger(BatchConfig.class);
    // 注入的是 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
    // 可自定义 bean 如设置核心线程数、最大线程数、队列大小等
    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;

    public BatchConfig(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.threadPoolTaskExecutor = threadPoolTaskExecutor;
    }

    // job 注册/存储器 用来注册 job 以及存储 job 相关信息
    @Bean
    public JobRepository myJobRepository(@Qualifier("dataSource") DataSource dataSource,
                                         PlatformTransactionManager transactionManager) throws Exception {
        logger.info("init job repository");
        JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();

        // 设置存储器数据源类型 默认为项目数据源
        factoryBean.setDatabaseType("mysql");
        logger.info("job repository databaseType is mysql");

        factoryBean.setDataSource(dataSource);
        factoryBean.setTransactionManager(transactionManager);
        return factoryBean.getObject();
    }

    // job 启动器 jobLauncher.run() 方法启动 job
    @Bean
    public JobLauncher myJobLauncher(@Qualifier("dataSource") DataSource dataSource,
                                     PlatformTransactionManager transactionManager) throws Exception {
        logger.info("init job launcher");
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
        jobLauncher.setJobRepository(myJobRepository(dataSource, transactionManager));

        // 设置任务执行方式 默认为同步执行
        jobLauncher.setTaskExecutor(threadPoolTaskExecutor);
        logger.info("job launcher executor is threadPoolTaskExecutor");

        return jobLauncher;
    }

    /**
     * 重试机制
     * 来自 spring 框架提供的 retry 模块
     *
     * 可构建各种重试策略 如 重试次数、重试超时时间、无限重试、退避策略等
     *      如果这些都不满足你 那你可以自定义重试策略
     *      退避策略指每次重试之后的等待时间（线程睡眠时间 以等待资源释放 如 io 资源等）
     *      可给重试机制添加自定义异常 监听器等
     *
     * 退避策略包括以下几个:
     *      指数退避策略: 按照某种规则计算每次重试之后的等待时间
     *      指数随机数退避策略: 在指数退避策略的计算中加入随机数
     *      固定退避策略: 即每次重试之后的等待时间固定
     *      统一随机数退避策略: 即每次重试之后的等待时间随机于指定范围内
     *      无退避策略: 即每次重试之后不需要等待 立即重试
     *      如果这些都不满足你 那你可以自定义退避策略
     *
     * 重试 5 次 每次重试之后不等待
     * return new RetryTemplateBuilder()
     *                 .maxAttempts(5)
     *                 .build();
     *
     * 重试 10 秒 每次重试之后不等待
     * return new RetryTemplateBuilder()
     *                 .withinMillis(10000)
     *                 .build();
     *
     * 无限重试 每次重试之后不等待
     * return new RetryTemplateBuilder()
     *                 .infiniteRetry()
     *                 .build();
     *
     * 重试 5 次 每次重试之后等待 5 秒
     * return new RetryTemplateBuilder()
     *                 .maxAttempts(5)
     *                 .fixedBackoff(5000)
     *                 .build();
     *
     * 重试 5 次 每次重试之后等待时间计算公式为
     *      currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)
     *      第一次等待 Math.min(1000 * 2^0, 20 * 1000) = 1000 ms = 1 s
     *      第二次等待 Math.min(1000 * 2^1, 20 * 1000) = 2000 ms = 2 s
     *      第三次等待 Math.min(1000 * 2^2, 20 * 1000) = 4000 ms = 4 s
     *      第四次等待 Math.min(1000 * 2^3, 20 * 1000) = 8000 ms = 8 s
     * return new RetryTemplateBuilder()
     *                 .maxAttempts(5)
     *                 .exponentialBackoff(1000, 2, 20 * 1000)
     *                 .build();
     * @return
     */
    @Bean
    public RetryTemplate retryTemplate() {
        return new RetryTemplateBuilder()
                .maxAttempts(5)
                .exponentialBackoff(1000, 2, 20 * 1000)
                .build();

    }

}
